package com.business.java2cache.redis;

import com.business.java2cache.core.Cache;
import com.business.java2cache.core.CacheConstans;
import com.business.java2cache.core.Java2Cache;
import com.business.java2cache.serializer.SerializationUtils;
import com.business.java2cache.util.CacheException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.util.List;
import java.util.Properties;

/**
 * Redis 缓存基于Hashs实现
 */
public class RedisCache implements Cache {

    private final static Logger log = LoggerFactory.getLogger(RedisCache.class);

    private static RedisCache redisCache = new RedisCache();

    public static JedisPool pool;

    public static RedisCache getInstance() {
        return redisCache;
    }

    private RedisCache() {
        JedisPoolConfig config = new JedisPoolConfig();
        Properties props = Java2Cache.getConfig();
        String host = getProperty(props, "redis.host", "127.0.0.1");
        String password = props.getProperty("redis.password", "");

        int port = getProperty(props, "redis.port", 6379);
        int timeout = getProperty(props, "redis.timeout", 2000);
        int database = getProperty(props, "redis.database", 0);

        config.setBlockWhenExhausted(getProperty(props, "redis.blockWhenExhausted", true));
        config.setMaxIdle(getProperty(props, "redis.maxIdle", 10));
        config.setMinIdle(getProperty(props, "redis.minIdle", 5));
//		config.setMaxActive(getProperty(props, "redis.maxActive", 50));
        config.setMaxTotal(getProperty(props, "redis.maxTotal", 10000));
        config.setMaxWaitMillis(getProperty(props, "redis.maxWait", 100));
        config.setTestWhileIdle(getProperty(props, "redis.testWhileIdle", false));
        config.setTestOnBorrow(getProperty(props, "redis.testOnBorrow", true));
        config.setTestOnReturn(getProperty(props, "redis.testOnReturn", false));
        config.setNumTestsPerEvictionRun(getProperty(props, "redis.numTestsPerEvictionRun", 10));
        config.setMinEvictableIdleTimeMillis(getProperty(props, "redis.minEvictableIdleTimeMillis", 1000));
        config.setSoftMinEvictableIdleTimeMillis(getProperty(props, "redis.softMinEvictableIdleTimeMillis", 10));
        config.setTimeBetweenEvictionRunsMillis(getProperty(props, "redis.timeBetweenEvictionRunsMillis", 10));
        config.setLifo(getProperty(props, "redis.lifo", false));

        pool = new JedisPool(config, host, port, timeout, password, database);

        log.info("++++++ RedisCache init success");

    }

    /**
     * 在region里增加一个可选的层级,作为命名空间,使结构更加清晰
     * 同时满足小型应用,多个Java2Cache共享一个redis database的场景
     *
     * @param name
     * @return
     */
    private String appendNameSpace(String name) {
        String nameSpace = CacheConstans.namespace;
        if (nameSpace != null && !nameSpace.isEmpty()) {
            name = nameSpace + ":" + name;
        }
        return name;
    }

    protected byte[] getKeyNameBytes(String key) {
        return key.getBytes();
    }


    /**
     * Get an item from the cache, nontransactionally
     * region不为空。查询hash
     * region为空 直接返回get(key)
     *
     * @param region cache region
     * @param key    cache key
     * @return the cached object or null
     */
    @Override
    public Object get(String region, String key) throws CacheException {
        if (null == key)
            return null;
        if (region == null) {//直接获取值
            return _get(key);
        }
        region = appendNameSpace(region);
        Object obj = null;
        try (Jedis cache = pool.getResource()) {
            byte[] b = cache.hget(region.getBytes(), getKeyNameBytes(key));
            if (b != null)
                obj = SerializationUtils.deserialize(b);
        } catch (Exception e) {
            log.error("Error occured when get data from redis2 cache", e);
            if (e instanceof IOException || e instanceof NullPointerException)
                remove(region, key);
        }
        return obj;

    }

    /**
     * Get an item from the cache, nontransactionally
     * region use default value
     *
     * @param key cache key
     * @return the cached object or null
     */
    public Object _get(String key) throws CacheException {
        String _key = appendNameSpace(key);
        Object obj = null;
        try (Jedis cache = pool.getResource()) {
            byte[] b = cache.get(getKeyNameBytes(_key));
            if (b != null)
                obj = SerializationUtils.deserialize(b);
        } catch (Exception e) {
            log.error("Error occured when get data from redis2 cache", e);
            if (e instanceof IOException || e instanceof NullPointerException)
                _remove(key);
        }
        return obj;
    }

    /**
     * Add an item to the cache, nontransactionally, with
     * failfast semantics
     *
     * @param region
     * @param key     cache key
     * @param value   cache value
     * @param seconds cache Expiration time
     */
    @Override
    public void set(String region, String key, Object value, int seconds) throws CacheException {
        if (key == null)
            return;
        if (value == null)
            remove(region, key);
        else if (region == null) {
            _set(key, seconds, value);
        } else {
            String _region = appendNameSpace(region);
            try (Jedis cache = pool.getResource()) {
                cache.hset(_region.getBytes(), getKeyNameBytes(key), SerializationUtils.serialize(value));
            } catch (Exception e) {
                throw new CacheException(e);
            }
        }
    }

    /**
     * 设置超时时间
     *
     * @param key     键
     * @param seconds 时间（秒） 60*60为一小时
     * @param value   值
     * @return
     */
    public void _set(String key, int seconds, Object value) {
        if (key == null)
            return;
        if (value == null)
            _remove(key);
        else {
            String _key = appendNameSpace(key);
            try (Jedis cache = pool.getResource()) {
                cache.setex(getKeyNameBytes(_key), seconds, SerializationUtils.serialize(value));
            } catch (Exception e) {
                throw new CacheException(e);
            }
        }
    }

    /**
     * @param key Cache key
     *            Remove an item from the cache
     */
    @Override
    public void remove(String region, Object key) throws CacheException {
        if (key == null)
            return;
        if (region == null) {
            _remove(key);
        } else {
            if (key instanceof List) {
                List keys = (List) key;
                try (Jedis cache = pool.getResource()) {
                    int size = keys.size();
                    byte[][] okeys = new byte[size][];
                    for (int i = 0; i < size; i++) {
                        String _key = appendNameSpace(keys.get(i).toString());
                        okeys[i] = getKeyNameBytes(_key);
                    }
                    cache.del(okeys);
                } catch (Exception e) {
                    throw new CacheException(e);
                }
            } else {
                region = appendNameSpace(region);
                try (Jedis cache = pool.getResource()) {
                    cache.hdel(region.getBytes(), getKeyNameBytes(key.toString()
                    ));
                } catch (Exception e) {
                    throw new CacheException(e);
                }
            }

        }

    }

    /**
     * @param key Cache key
     *            Remove an item from the cache
     */
    public void _remove(Object key) throws CacheException {
        if (key == null)
            return;
        String _key = appendNameSpace(key.toString());
        try (Jedis cache = pool.getResource()) {
            cache.del(getKeyNameBytes(_key));
        } catch (Exception e) {
            throw new CacheException(e);
        }
    }

    /**
     * Clear the cache
     */
    @Override
    public void clear(String region) throws CacheException {
        try (Jedis cache = pool.getResource()) {
            region = appendNameSpace(region);
            cache.del(getKeyNameBytes(region));
        } catch (Exception e) {
            throw new CacheException(e);
        }
    }

    /**
     * update exprie time
     *
     * @param key
     * @param seconds
     */
    public Object exprie(String region, String key, int seconds) {
        if (key == null)
            return null;
        if (region == null || region.isEmpty()) {
            String _key = appendNameSpace(key);
            try (Jedis cache = pool.getResource()) {
                return cache.expire(getKeyNameBytes(_key), seconds);
            } catch (Exception e) {
                throw new CacheException(e);
            }
        } else {
            return get(region, key);
        }
    }

    //###################################################


    public Jedis _getJedis() {
        Jedis cache = pool.getResource();
        return cache;
    }

    private static String getProperty(Properties props, String key, String defaultValue) {
        return props.getProperty(key, defaultValue).trim();
    }

    private static int getProperty(Properties props, String key, int defaultValue) {
        try {
            return Integer.parseInt(props.getProperty(key, String.valueOf(defaultValue)).trim());
        } catch (Exception e) {
            return defaultValue;
        }
    }

    private static boolean getProperty(Properties props, String key, boolean defaultValue) {
        return "true".equalsIgnoreCase(props.getProperty(key, String.valueOf(defaultValue)).trim());
    }

}
